﻿/*
This project and all of its code is/are Copyright (C) 2017  Nils Hunter Alving

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
using DynamicDependencyContainer.Interfaces;
using System;
using System.Linq;

namespace DynamicDependencyContainer.Models
{
    public class RegisteredInterfaceType : RegisteredTypeBase
    {
        protected virtual object Instance { get; set; }

        /// <summary>
        /// Registers the specified class as the type to be used to represent the specified interface
        /// </summary>
        /// <param name="interfaceImplemented">The interface that is being registered</param>
        /// <param name="implementingClass">The class that implements the interface</param>
        /// <param name="dependencyResolver">The object that will be used to resolve dependencies when the Resolve() method is invoked</param>
        /// <exception cref="ArgumentException"></exception>
        public RegisteredInterfaceType(Type interfaceImplemented, Type implementingClass, IResolveDependencies dependencyResolver)
            :base(interfaceImplemented, dependencyResolver)
        {
            Initialize(interfaceImplemented, implementingClass);
            SetConstructor(Type.EmptyTypes);
        }
        /// <summary>
        /// Returns either the registered instance or a new instance of the registered implementing class
        /// </summary>
        /// <exception cref="InvalidOperationException"></exception>
        /// <returns>An instance of the implementing class</returns>
        public override object Resolve()
        {
            if (Instance != null)
            {
                return Instance;
            }
            if (Constructor == null)
            {
                throw new Exception($"Attempt to resolve interface: {RegisteredInterface} with type: {ImplementingClass} cannot be processed. The instance is null and no constructor was specified when the instance was registered");
            }

            return CreateObject();
        }
        protected virtual object CreateObject()
        {
            var instance = ConstructorArguments == null ? New() : New(ConstructorArguments.ToArray());
            return instance;
        }
        /// <summary>
        /// Invokes the default constructor on the implementing class
        /// </summary>
        /// <exception cref="InvalidOperationException"></exception>
        /// <returns>An instance of the implementing class</returns>
        internal virtual object New()
        {
            try
            {
                return Activator.CreateInstance(ImplementingClass);
            }
            catch (Exception e)
            {
                throw new InvalidOperationException($"Attempting to create an instance of type {ImplementingClass} with the default constructor threw exception: {e.GetType()}, {e.Message}");
            }
        }
        /// <summary>
        /// Invokes the constructor on the implementing class that has a matching list of parameters
        /// </summary>
        /// <param name="constructorArgs"></param>
        /// <exception cref="InvalidOperationException"></exception>
        /// <returns>An instance of the implementing class</returns>
        internal virtual object New(object[] constructorArgs)
        {
            try
            {
                return Activator.CreateInstance(ImplementingClass, constructorArgs);
            }
            catch (Exception e)
            {
                var argList = string.Join(", ", constructorArgs.Select(a => a.GetType().Name));
                throw new InvalidOperationException($"Attempting to create an instance of type {ImplementingClass} with arguments of type {argList} threw exception: {e.GetType()}, {e.Message}");
            }
        }
    }
}
